//
//  ChangesPipeline.swift
//  GerritHermes
//
//  Created by J.Zhou on 2020/6/22.
//  Copyright © 2020 周剑. All rights reserved.
//

import Foundation
import Combine

final class ChangesPipeline {
    typealias Callback = (Int) -> Void

    private let project: String
    private let gerritApi: GerritApi
    private let bag = CancellableBag()
    private var hitCount = 0

    private var callback: Callback?

    init(project: String, gerritApi: GerritApi) {
        self.project = project
        self.gerritApi = gerritApi
    }

    func process(_ callback: @escaping Callback) {
        self.callback = callback

        let project = self.project
        gerritApi
            .fetchChanges(project: project)
            .sink(receiveCompletion: { [weak self] (completion) in
                CompletionHandler.handle(completion, context: "Fetch <\(project)> Open Changes")
                Log.debug("Fetch <\(project)> Open Changes Completion: \(completion.isSuccess)")
                if !completion.isSuccess {
                    self?.callback?(0)
                }
            }, receiveValue: { [weak self] (openChanges) in
                self?.handle(subjects: openChanges)
            }).cancel(by: bag)
    }

    private func handle(subjects: [ChangeInfo]) {
        // 1. 拉取到所有需要review的changes details
        // 2. 将新的changes和记录的changes分类成3个集合：past、now和future
        //   past: 本地数据中不存在于changes中的数据
        //   now: changes中存在于本地数据中的数据
        //   future: 本地数据中没有的数据
        // 3. past: 肯定是已经merge或者abondoned掉的，对于每一条change
        //   i. 查询change具体信息:
        //      如果是owner merge的，且除了owner没有其他人vote，则需要警告reviewers和cc是owner自己合入的代码；
        //      如果merged了且不是owner做的，则通知owner'谁'merge了代码
        //      如果abondone了，则通知reviewers；
        //  ii. 从记录中删除该change
        // iii. 从记录中删除对应changeId的所有comments和messages
        // 4. now: 已经处于review中的数据
        //   i. 如果之前是mergable，而现在不是，则通知owner需要处理冲突
        //  ii. diff reviewers，通知新增的reviewer、CC和assignee
        // iii. 从messages中提取评论信息，发出新message通知
        //  vi. 遍历每一条未处理的comments，如果是评论则通知owner，如果是回复则通知评论作者和owner
        //   v. 将记录更新到数据库
        // 5. future: 新的review
        //   i. 如果创建时间未超时，则通知用户有新的review到达
        //  ii. 通知用户新的messages到达
        // iii. 通知用户新的comments到达
        //  iv. 通知owner是否需要解决冲突
        //   v. 从labels中提取vote信息，通知用户vote的变化
        //  vi. 将记录添加到数据库中
        guard !subjects.isEmpty else {
            callback?(0)
            return
        }

        Log.info("Fetch change details: \(subjects.map { $0.subject })")

        let project = self.project
        let sequence = subjects
            .map { gerritApi.fetchChangeDetail(id: $0.id).delay(for: .milliseconds(300), scheduler: DispatchQueue.main) }
        Publishers.Sequence(sequence: sequence)
            .flatMap { $0 }
            .materialize()
            .collect()
            .sink(receiveValue: { [weak self] changes in
                let validChanges = changes.compactMap { event -> ChangeInfo? in
                    switch event {
                    case .value(let change): return change
                    case .failure(let error):
                        CompletionHandler.handle(error, context: "Fetch <\(project)> Detail Changes")
                        return nil
                    default: return nil
                    }
                }
                self?.prismHandle(validChanges)
            }).cancel(by: bag)
    }

     private func prismHandle(_ changes: [ChangeInfo])  {
        do {
            let savedChanges = try ChangeInfo.select(related: project)
            let (past, now, future) = savedChanges.split(new: changes) { $0.id == $1.id }
            Publishers
                .Sequence(sequence: [handlePast(past), handleNow(now), handleFuture(future)])
                .flatMap { $0 }
                .sink(receiveCompletion: { [weak self] (completion) in
                    self?.callback?(self?.hitCount ?? 0)
                    self?.hitCount = 0
                }, receiveValue: { () in
                }).cancel(by: bag)
        } catch let error {
            Log.error("数据库查询失败", context: error)
        }
    }

    private func handlePast(_ changes: [ChangeInfo]) -> AnyPublisher<Void, Never> {
        guard !changes.isEmpty else { return Empty().eraseToAnyPublisher() }

        let subject = PassthroughSubject<Void, Never>()
        deleteRelated(changes)
        let sequence = changes.map { gerritApi.fetchChangeDetail(id: $0.id).delay(for: .milliseconds(300), scheduler: DispatchQueue.main) }
        Publishers.Sequence(sequence: sequence)
            .flatMap { $0 }
            .sink(receiveCompletion: { (completion) in
                subject.send(completion: .finished)
            }, receiveValue: { [weak self] changeInfo in
                switch changeInfo.status {
                case .merged:
                    guard let submitter = changeInfo.submitter else { return }
                    if submitter == changeInfo.owner {
                        guard let approved = changeInfo.labels?.codeReview.approved,
                            let approvalInfos = changeInfo.labels?.codeReview.all,
                            approved == changeInfo.owner else { return }
                        // 如果是owner merge的，且除了owner没有其他人vote，则需要警告reviewers和cc是owner自己合入的代码；
                        if approvalInfos.reduce(0, { $0 + ($1.value ?? 0) }) == 2 {
                            Hermes.send(
                                to: changeInfo.allReceivers,
                                changeInfo.messageTitle,
                                "👹 Merged by owner \(changeInfo.owner.sender)",
                                formatGerritAddress(changeInfo.gerritAddress)
                            )
                        }
                    } else {
                        // 如果merged了且不是owner做的，则通知owner'谁'merge了代码
                        Hermes.send(
                            to: [changeInfo.owner],
                            changeInfo.messageTitle,
                            "😁 Merged by \(submitter.sender)",
                            formatGerritAddress(changeInfo.gerritAddress)
                        )
                    }
                case .abandoned:
                    guard let messages = changeInfo.messages else { return }
                    for message in messages.reversed() where message.message.hasPrefix("Abandoned") {
                        // abondone一般只有owner有权限
                        let author = message.author ?? changeInfo.owner
                        Hermes.send(
                            to: changeInfo.reviewers?.reviewer ?? [],
                            changeInfo.messageTitle,
                            "😨 Abondoned by \(author.sender)",
                            formatGerritAddress(changeInfo.gerritAddress)
                        )
                        break
                    }
                default: break
                }
                self?.hitCount += 1
            }).cancel(by: bag)

        return subject.eraseToAnyPublisher()
    }

    private func handleNow(_ changes: [(ChangeInfo, ChangeInfo)]) -> AnyPublisher<Void, Never> {
        guard !changes.isEmpty else { return Empty().eraseToAnyPublisher() }

        let subject = PassthroughSubject<Void, Never>()

        let futures = changes.map { (tuple) -> AnyPublisher<Void, Never> in
            let (old, new) = tuple
            let future: Future<Void, Never> = Future { [unowned self] (promise) in
                var needUpdate = false
                defer {
                    if needUpdate {
                        self.hitCount += 1
                        do {
                            try ChangeInfo.insertOrReplace(new)
                        } catch let error {
                            Log.error("数据库插入失败", context: error)
                        }
                    }
                }

                // 如果之前是mergable，而现在不是，则通知owner需要处理冲突
                if let oldMergable = old.mergeable, let newMergable = new.mergeable, oldMergable, !newMergable {
                    self.notifyConflict(change: new)
                    needUpdate = true
                }

                // 如果没有更新则直接跳过
                guard old.updated != new.updated else {
                    promise(.success(()))
                    return
                }
                needUpdate = true

                // diff reviewers，通知新增的reviewer、CC和assignee
                let (reviewers, cc, assignee) = ChangeInfo.diff(old: old, new: new)
                if reviewers.count > 0 {
                    Hermes.send(
                        to: reviewers,
                        new.messageTitle,
                        "😀 Add you as reviwer",
                        formatGerritAddress(new.gerritAddress)
                    )
                }
                if cc.count > 0 {
                    Hermes.send(
                        to: cc,
                        new.messageTitle,
                        "😀 Add you as cc",
                        formatGerritAddress(new.gerritAddress)
                    )
                }
                if let assignee = assignee {
                    Hermes.send(
                        to: [assignee],
                        new.messageTitle,
                        "😀 Add you as assignee",
                        formatGerritAddress(new.gerritAddress)
                    )
                }

                // 从messages中提取评论信息，发出新message通知
                let oldMessageCount = old.messages?.count ?? 0
                if let newMessages = new.messages, newMessages.count > oldMessageCount {
                    let messages = newMessages[oldMessageCount...]
                    self.notify(change: new, messages: messages)
                }

                // 遍历每一条未处理的comments，如果是评论则通知owner，如果是回复则通知评论作者和owner
                self.fetchComments(change: new)
                    .sink(receiveCompletion: { (completion) in
                        promise(.success(()))
                    }, receiveValue: { () in
                    }).cancel(by: self.bag)
            }
            return future.eraseToAnyPublisher()
        }

        Publishers
            .Sequence(sequence: futures)
            .flatMap { $0 }
            .sink(receiveCompletion: { (completion) in
                subject.send(completion: .finished)
            }, receiveValue: { _ in
            }).cancel(by: bag)
        return subject.eraseToAnyPublisher()
    }

    private func handleFuture(_ changes: [ChangeInfo]) -> AnyPublisher<Void, Never> {
        guard !changes.isEmpty else { return Empty().eraseToAnyPublisher() }

        let subject = PassthroughSubject<Void, Never>()
        // 将记录添加到数据库中
        do {
            try ChangeInfo.insertOrReplace(changes)

            let futures = changes.map { (change) -> AnyPublisher<Void, Never> in
                let future: Future<Void, Never> = Future { [unowned self] (promise) in
                    // 如果创建时间未超时，则通知用户有新的review到达
                    guard change.isNewChange else {
                        promise(.success(()))
                        return
                    }
                    Hermes.send(
                        to: change.allReceivers,
                        change.messageTitle,
                        "📨 收到新Review",
                        formatGerritAddress(change.gerritAddress)
                    )

                    // 处理messages
                    if let messages = change.messages {
                        self.notify(change: change, messages: messages[...])
                    }

                    // 通知owner是否需要解决冲突
                    if let mergable = change.mergeable, !mergable {
                        self.notifyConflict(change: change)
                    }

                    // 从labels中提取vote信息
                    self.notifyVote(oldReview: nil, change: change)

                    // 处理comments
                    self.fetchComments(change: change)
                        .sink(receiveCompletion: { (completion) in
                            promise(.success(()))
                        }, receiveValue: { () in
                        }).cancel(by: self.bag)
                }
                return future.eraseToAnyPublisher()
            }

            Publishers
                .Sequence(sequence: futures)
                .flatMap { $0 }
                .sink(receiveCompletion: { (completion) in
                    subject.send(completion: .finished)
                }, receiveValue: { _ in
                }).cancel(by: bag)
        } catch let error {
            Log.error("数据库插入失败", context: error)
            subject.send(completion: .finished)
        }
        return subject.eraseToAnyPublisher()
    }

    private func notifyVote(oldReview: CodeReview?, change: ChangeInfo)  {
        if let voteInfo = CodeReview.diff(old: oldReview, new: change.labels?.codeReview), voteInfo.account != change.owner {
            Hermes.send(
                to: [change.owner],
                change.messageTitle,
                "\(voteInfo.account.sender) \(voteInfo.vote.message)",
                formatGerritAddress(change.gerritAddress)
            )
        }
    }

    private func notifyConflict(change: ChangeInfo) {
        Hermes.send(
            to: [change.owner],
            "😱 [Conflict] \(change.messageTitle)",
            formatGerritAddress(change.gerritAddress)
        )
    }

    private func notify(change: ChangeInfo, messages: ArraySlice<ChangeMessageInfo>) {
        let toOwnerMsgs = messages
            .filter {
                #if DEBUG
                switch DebugModel.shared.mode {
                case .developerOnly:
                    return $0.messageType.notifyOwner
                default:
                    return $0.messageType.notifyOwner && $0.author != change.owner
                }
                #else
                return $0.messageType.notifyOwner && $0.author != change.owner
                #endif
            }
            .map { ($0.author?.sender ?? "") + " commented a message:\n" + $0.message }
        if toOwnerMsgs.count > 0 {
            Hermes.send(
                to: [change.owner],
                change.messageTitle,
                toOwnerMsgs.joined(separator: "\n---------\n"),
                formatGerritAddress(change.gerritAddress)
            )
        }

        if let patchSetInfo = messages.reversed().first(where: { $0.messageType.isPatchSet }),
            let patchSetNumber = patchSetInfo.messageType.patchSetNumber,
            patchSetNumber > 1, !patchSetInfo.messageType.isRebase
        {
            let gerritAddress = "\(change.gerritAddress)/\(patchSetNumber - 1)..\(patchSetNumber)"
            Hermes.send(
                to: change.allReceivers,
                change.messageTitle,
                (patchSetInfo.author?.sender ?? "") + "\n" + patchSetInfo.message,
                formatGerritAddress(gerritAddress)
            )
        }
    }

    private func fetchComments(change: ChangeInfo) -> AnyPublisher<Void, Never> {
        let subject = PassthroughSubject<Void, Never>()
        let info = "\(project) - \(change.subject)"
        gerritApi
            .fetchComments(id: change.id)
            .sink(receiveCompletion: {
                CompletionHandler.handle($0, context: "Fetch <\(info)> Comments")
                subject.send(completion: .finished)
            }, receiveValue: { [weak self] (comments) in
                self?.handle(change: change, comments: comments)
            }).cancel(by: bag)
        return subject.eraseToAnyPublisher()
    }

    private func handle(change: ChangeInfo, comments: [CommentInfo]) {
        let newComments: [CommentInfo]
        do {
            let savedComments = try CommentInfo.select(comments)
            newComments = CommentInfo.newComments(old: savedComments, all: comments)
        } catch let error {
            newComments = []
            Log.error("数据库查询失败", context: error)
        }

        do {
            try CommentInfo.insertOrReplace(newComments, relatedKey: change.id)
            hitCount += newComments.count
            // TODO: Comments聚合
            for comment in newComments where comment.isNewComment {
                guard let commentMsg = comment.message, let author = comment.author else { continue }
                let location = comment.location
                let commentMessage = location.isEmpty ? commentMsg : "\(location) \(commentMsg)"
                let gerritAddress = "\(change.gerritAddress)\(comment.gerritPath)"
                if let inRelayTo = comment.inReplyTo, let replyComment = comments.first(where: { $0.id == inRelayTo }), let replyUser = replyComment.author {
                    Hermes.send(
                        to: [replyUser],
                        change.messageTitle,
                        """
                        📝 \(author.sender) replied you at: \(commentMessage)

                        ====Quoted Comment====
                        \(replyComment.message ?? "")
                        ====Quoted Comment====
                        """,
                        formatGerritAddress(gerritAddress)
                    )
                } else {
                    let receivers: [AccountInfo]
                    if change.owner == author {
                        receivers = change.allReceivers
                    } else {
                        receivers = [change.owner]
                    }
                    Hermes.send(
                        to: receivers,
                        change.messageTitle,
                        "📝 \(author.sender) comment on: \(commentMessage)",
                        formatGerritAddress(gerritAddress)
                    )
                }
            }
        } catch let error {
            Log.error("数据库插入失败", context: error)
        }
    }

    private func deleteRelated(_ changes: [ChangeInfo]) {
        for change in changes {
            do {
                try ChangeInfo.inTransaction {
                    try ChangeInfo.delete(change)
                    try CommentInfo.delete(related: change.id)
                }
            } catch let error {
                Log.error("执行数据库事务失败", context: error)
            }
        }
    }

}

private func formatGerritAddress(_ address: String) -> String {
    return "📌 \(address)"
}
